package sskj.regulatory.domain.fetcher

import com.bumptech.glide.request.RequestListener
import com.google.gson.JsonParser
import com.google.gson.annotations.Until
import io.reactivex.*
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.CompositeDisposable
import io.reactivex.schedulers.Schedulers
import retrofit2.HttpException
import sskj.regulatory.data.response.Response
import sskj.regulatory.data.response.session.TokenResponse
import sskj.regulatory.domain.fetcher.result_listener.RequestType
import sskj.regulatory.domain.fetcher.result_listener.ResultListener
import java.net.ConnectException
import java.util.concurrent.ConcurrentHashMap
import javax.inject.Inject
import javax.inject.Singleton


@Singleton
class Fetcher @Inject constructor(private val disposable: CompositeDisposable) {

    private val requestMap = ConcurrentHashMap<RequestType, Status>()

    private fun <T> getIOToMainTransformer(): SingleTransformer<T, T> {
        return SingleTransformer {
            it.subscribeOn(Schedulers.io())
                    .observeOn(AndroidSchedulers.mainThread())
        }
    }


    fun <T> run(flowable: Flowable<T>, scheduler: Scheduler, success: (T) -> Unit, error: (Throwable) -> Unit) {
        disposable.add(flowable
                .subscribeOn(scheduler)
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(success, error))
    }

    fun <T> run(flowable: Flowable<T>, success: (T) -> Unit, error: (Throwable) -> Unit) {
        disposable.add(flowable
                .subscribeOn(AndroidSchedulers.mainThread())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(success, error))
    }

    fun <T> fetch(flowable: Flowable<T>, requestType: RequestType,
                  resultListener: ResultListener, success: (T) -> Unit) {
        disposable.add(flowable
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .doOnSubscribe { resultListener startAndAdd requestType }
                .subscribe(onSuccess<T>(requestType, resultListener, success),
                        resultListener.onError(requestType)))
    }

    fun <T> fetch(observable: Observable<T>, requestType: RequestType,
                  resultListener: ResultListener, success: (T) -> Unit) {
        disposable.add(observable
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .doOnSubscribe { resultListener startAndAdd requestType }
                .subscribe(onSuccess<T>(requestType, resultListener, success),
                        resultListener.onError(requestType)))
    }

    fun <T> fetch(single: Single<T>, requestType: RequestType,
                  resultListener: ResultListener, success: (T) -> Unit) {
        disposable.add(single
                .compose(getIOToMainTransformer())
                .doOnSubscribe { resultListener startAndAdd requestType }
                .subscribe(onSuccess<T>(requestType, resultListener, success),
                        resultListener.onError(requestType)))
    }

    fun complete(completable: Completable, requestType: RequestType,
                 resultListener: ResultListener, success: () -> Unit) {
        disposable.add(completable
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .doOnSubscribe { resultListener startAndAdd requestType }
                .subscribe({
                    requestMap.replace(requestType, Status.SUCCESS)
                    success()
                }, resultListener.onError(requestType)))
    }

    private infix fun ResultListener.startAndAdd(requestType: RequestType) {
        onRequestStart(requestType)
        if (requestType != RequestType.TYPE_NONE)
            requestMap[requestType] = Status.LOADING


    }

    private fun ResultListener.onError(requestType: RequestType): (Throwable) -> Unit {
        return {
            requestMap.replace(requestType, Status.ERROR)
            it.printStackTrace()
            when (it) {
                is HttpException -> {
//                    log { it.response().errorBody()?.string() }
                    val content = it.response().errorBody()?.string()
                    var msg: String? = ""
                    var status: Int? = 0
                    when (content.isNullOrEmpty()) {
                        true -> {
                            msg = it.message()
                            status = it.code()
                        }
                        false -> {
                            msg = JsonParser().parse(content)?.asJsonObject?.get("data")?.asJsonObject?.get("message")?.asString;
                            status = JsonParser().parse(content)?.asJsonObject?.get("data")?.asJsonObject?.get("status")?.asInt;
                        }
                    }

                    when (status) {
                        401 -> {
                            onSessionError()
                        }
                        else -> {
                            onRequestError(requestType, status.toString(), msg)
                        }
                    }

                }
                is ConnectException -> {
                    onConnectError()
                }
                is java.net.SocketTimeoutException -> {
                    onConnectError()
                }
                else -> {
                    onRequestError(requestType, "", it.message)
                }
            }
        }
    }



    private fun <T> onSuccess(requestType: RequestType, resultListener: ResultListener, success: (T) -> Unit): (T) -> Unit {
        return {

            when (it) {
                is TokenResponse -> {
                    when (it.success) {
                        true -> {
                            val status = if (it is List<*> && it.isEmpty()) {
                                Status.EMPTY_SUCCESS
                            } else {
                                Status.SUCCESS
                            }
                            requestMap.replace(requestType, status)
                            success(it)
                        }
                        else -> {
                            resultListener.onRequestError(it.errorCode, it.msg)
                        }
                    }
                }
                is Response -> {
                    when (it.success) {
                        true -> {
                            val status = if (it is List<*> && it.isEmpty()) {
                                Status.EMPTY_SUCCESS
                            } else {
                                Status.SUCCESS
                            }
                            requestMap.replace(requestType, status)
                            success(it)
                        }
                        else -> {
                            resultListener.onRequestError(it.errorCode, it.msg)
                        }
                    }
                } else -> {
                requestMap.replace(requestType,  Status.SUCCESS)
                success(it)
            }
            }
        }
    }

    fun hasActiveRequest(): Boolean = requestMap.isNotEmpty()

    fun getRequestStatus(requestType: RequestType) = requestMap.getOrElse(requestType, { Status.IDLE })

    fun removeRequest(requestType: RequestType) {
        requestMap.remove(requestType)
    }

    fun clear() {
        disposable.clear()
    }
}